
const FREEZE_TIMER = 5;

export class GotoOpts{

    /**
     * 
     * @param {GotoOpts|MoveToOpts} opts 
     */
    constructor(opts){
        {
            this.reuseTarget = true;
            this.closeRange = 5;
            this.dinamicPathReuse = true;
            this.pathLengthRangeRatio = 1.5;
            this.minPathReuse = 5;
            this.maxPathReuse = 500;
        }

        if(typeof opts === "object"){
            for(const name in opts){
                if(this[name] === undefined){
                    this[name] = opts[name];
                }
            }
        }
    }
    parse(creep, target){
        let opts = {};
        for(let name in this){
            if(this[name] !== undefined){
                opts[name] = this[name];
            }
        }

        if(opts.dinamicPathReuse){
            const range = creep.pos.getRangeTo(target);
            if(range<=opts.closeRange){
                opts.reusePath = opts.minPathReuse;
            }else{
                opts.reusePath = Math.ceil(Math.min(opts.maxPathReuse, range * opts.pathLengthRangeRatio));
            }
        }

        if(opts.ignoreCreeps===undefined || opts.reuseTarget){
            const isCloseInRange=creep.pos.inRangeTo(target.pos, opts.closeRange);
            if(typeof creep.memory.goto.freezeTimer === "number" && creep.memory.goto.freezeTimer < 0){
                opts.ignoreCreeps=false;
                opts.reusePath = 10;
                // creep.memory.goto.freezeTimer=FREEZE_TIMER;//todo: why?
            }else if(isCloseInRange){
                opts.ignoreCreeps=false;
            }else{
                opts.ignoreCreeps=true;
            }
        }

        return opts;
    }
}

export const jobGoto={
    /**
     * @param {Creep} creep
     * @param {RoomObject[]|{pos:RoomPosition}} targets
     * @param {undefined | GotoOpts | MoveToOpts} opts
    */
    run: function(creep, targets, opts=undefined){

        if(creep.fatigue > 0){
            return OK;
        }

        let ret=ERR_NOT_FOUND;
        let mem=this.getMemory(creep);
        let memTargetId=mem.target;


        let blocked = false;
        let lastPos = creep.memory.lastPos;
        creep.memory.lastPos = creep.pos;
        if(!lastPos){
            blocked = false;
        }else if(creep.pos.x === lastPos.x && creep.pos.y === lastPos.y && creep.pos.roomName === lastPos.roomName){
            blocked = true;
        }
        if(blocked){
            mem.freezeTimer--;
        }else{
            mem.freezeTimer = FREEZE_TIMER;
        }

        if(targets.length===0){
            mem.target = undefined;
            return ERR_INVALID_TARGET;
        }

        opts = new GotoOpts(opts);
        if(memTargetId !== undefined && opts.reuseTarget){
            for(let i = 0; i < targets.length; i++){
                const target = targets[i];
                if(target.id !== memTargetId){
                    continue;
                }
                if(creep.pos.isEqualTo(target.pos)){
                    return OK;
                }
                ret = creep.moveTo(target, opts.parse(creep, target));
                if(ret === OK){
                    return OK;
                }
            }
            mem.target = undefined
        }

        if(ret !== OK){
            for(const target of targets){
                mem.target = target.id;
                if(creep.pos.isEqualTo(target.pos)){
                    return OK;
                }
                ret = creep.moveTo(target, opts.parse(creep, target));
                if(ret === OK){
                    return OK;
                }
            }
        }

        mem.target = undefined;
        return ret;
    },
    getTarget(creep, err=false){
        try{
            return creep.memory.goto.target;
        }catch(e){
            if(err) throw e;
            return undefined;
        }
    },

    /**
     * 
     * @param {AnyCreep} creep 
     * @param {String|Id} target 
     * @param {Boolean} err 
     */
    setTargetId(creep, target){
        try{
            if(target.id !== undefined){
                target = target.id;
            }
            creep.memory.goto.target = target;
        }catch(e){
            this.getMemory(creep).target = target;
        }
    },
    getMemory(creep){
        let mem=creep.memory.goto;
        if(!mem){
            mem=creep.memory.goto={freezeTimer:FREEZE_TIMER};
        }
        if(!mem.freezeTimer){
            mem.freezeTimer = FREEZE_TIMER;
        }
        return mem;
    },
    /**
     * @param {Creep} creep
     * @param {RoomObject[]} targets
     * @param {boolean} decent
    */
    sortTargetsByDistance:function(creep, targets, decent=false){
        const pos=creep.pos;
        return targets.sort((a,b)=>{
            let dists=pos.distanceTo;
            if(dists===undefined){
                dists=pos.distanceTo={};
            }

            let distA=dists[a], distB=dists[b];
            if(distA==undefined){
                distA=dists[a]=pos.getRangeTo(a);
            }
            if(distB==undefined){
                distB=dists[b]=pos.getRangeTo(b);
            }

            if(decent){
                return distB-distA;
            }else{
                return distA-distB;
            }
        });
    },
    /**
     * @param {RoomObject} obj 
     * @param {RoomObject[]} targets 
     */
    getClosest(obj, targets){
        if(targets.length === 0)
            return undefined;

        return targets[this.getClosestIndex(obj, targets)];
    },
    getClosestIndex(obj, targets){
        if(targets.length===0){
            return -1;
        }
        const objPos = obj.pos;
        let targetRange = objPos.getRangeTo(targets[0].pos);
        let ind = 0;
        for(let i=1; i<targets.length; i++){
            const pos = targets[i].pos;
            const range = objPos.getRangeTo(pos);
            if(range<targetRange){
                targetRange = range;
                ind = i;
            }
        }
        return ind;
    }
}